/**
 * Copyright © 2014 Creativor Studio.
 * About license information, please see LICENSE.txt.
 */
package org.rayson.server.common.parser;

import org.rayson.api.KeyValues;
import org.rayson.api.exception.ProtocolException;
import org.rayson.share.http.HttpConstants;

/**
 * {@link ParameterParser} for Content-Type <b>
 * application/x-www-form-urlencoded</b>.
 * 
 * @author creativor
 */
class FormParameterParser implements ParameterParser {

	/**
	 * Convert a byte character value to hexadecimal digit value.
	 *
	 * @param b
	 *            the character value byte
	 */
	private static byte convertHexDigit(final byte b) {
		if ((b >= '0') && (b <= '9'))
			return (byte) (b - '0');
		if ((b >= 'a') && (b <= 'f'))
			return (byte) (b - 'a' + 10);
		if ((b >= 'A') && (b <= 'F'))
			return (byte) (b - 'A' + 10);
		throw new IllegalArgumentException("Not an hex digit: "
				+ Character.valueOf((char) b));
	}

	FormParameterParser() {

	}

	@Override
	public void parse(final byte[] data, final KeyValues target)
			throws ProtocolException {
		if (data == null)
			throw new IllegalArgumentException("data should not be null");
		if (target == null)
			throw new IllegalArgumentException("target should not be null");
		final int len = data.length;
		if (len == 0)
			return;

		int ix = 0;
		int ox = 0;
		String key = null;
		String value = null;
		while (ix < data.length) {
			final byte c = data[ix++];
			switch ((char) c) {
			case HttpConstants.AND_TOKEN:
				value = new String(data, 0, ox);
				if (key != null) {
					target.put(key, value);
					key = null;
				}
				ox = 0;
				break;
			case HttpConstants.EQUALS_TOKEN:
				if (key == null) {
					key = new String(data, 0, ox);
					ox = 0;
				} else {
					data[ox++] = c;
				}
				break;
			case HttpConstants.PLUS_TOKEN:
				data[ox++] = (byte) ' ';
				break;
			case HttpConstants.PERCENT_TOKEN:
				data[ox++] = (byte) ((convertHexDigit(data[ix++]) << 4) + convertHexDigit(data[ix++]));
				break;
			default:
				data[ox++] = c;
			}
		}
		// The last value does not end in '&'. So save it now.
		if (key != null) {
			value = new String(data, 0, ox);
			target.put(key, value);
		}
	}
}